﻿using Abp.AspNetCore.Mvc;
using Abp.Authorization;
using Abp.Authorization.Users;
using Abp.AutoMapper;
using Abp.Domain.Repositories;
using Abp.MultiTenancy;
using Abp.Runtime.Security;
using Abp.Runtime.Validation;
using Abp.UI;
using ALEXFW.Authorization;
using ALEXFW.Authorization.Roles;
using ALEXFW.Authorization.Users;
using ALEXFW.BasicModel;
using ALEXFW.Login.Dto;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Security.Claims;
using System.Threading.Tasks;

namespace ALEXFW.Login
{
    public class LoginAppService : ALEXFWAppServiceBase, ILoginAppService
    {
        private TokenConfiguration _configuration;
        private LogInManager _login;
        private IRepository<Student, Guid> _studentContext;
        private IRepository<Teacher, Guid> _teacherContext;
        private IRepository<School, Guid> _schoolContext;
        private IRepository<Group, Guid> _groupContext;
        private ITenantCache _tenantCache;

        public LoginAppService(
            IRepository<Student, Guid> studentContext,
            IRepository<Teacher, Guid> teacherContex,
            IRepository<School, Guid> schoolContext,
            IRepository<Group, Guid> groupContext,
            LogInManager logIn,
            ITenantCache tenantCache,
            TokenConfiguration configuration
            )
        {
            _login = logIn;
            _studentContext = studentContext;
            _teacherContext = teacherContex;
            _schoolContext = schoolContext;
            _groupContext = groupContext;
            _tenantCache = tenantCache;
            _configuration = configuration;
        }

        /// <summary>
        /// 学生登录接口
        /// </summary>
        /// <param name="account"></param>
        /// <returns></returns>
        public async Task<LoginDto> StudentLogin(AccountDto account)
        {
            if (account == null) throw new AbpValidationException();
            var loginResult = await _login.LoginAsync(account.UserNameOrEmailAddress, account.Password, GetTenancyNameOrNull());
            if (loginResult.Result == AbpLoginResultType.Success)
            {
                var roleName = await UserManager.GetRolesAsync(loginResult.User);
                if (roleName.Contains(StaticRoleNames.Host.Student))
                {
                    var accessToken = GetEncryptedAccessToken(CreateAccessToken(CreateJwtClaims(loginResult.Identity)));
                    var student = await _studentContext.FirstOrDefaultAsync(x => x.UserID == loginResult.User.Id);
                    var schoolName = (await _schoolContext.SingleAsync(x => x.Id == student.SchoolID)).Name;
                    var isGroup = false;
                    GroupInfoDto group = null;
                    //获取实习小组信息
                    if (student.GroupID != Guid.Empty)
                    {
                        isGroup = true;
                        var tmp = await _groupContext.SingleAsync(x => x.Id == student.GroupID);
                        var teacher = await _teacherContext.SingleAsync(x => x.Id == tmp.TeacherID);
                        group = new GroupInfoDto
                        {
                            Id = tmp.Id,
                            Name = tmp.Name,
                            TeacherName = teacher.Name,
                            TeacherPhone = teacher.PhoneNumber
                        };
                    }
                    return new LoginDto
                    {
                        AccessToken = accessToken,
                        ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds,
                        User = new UserInfoDto { Name = student.Name, isGroup = isGroup, SchoolName = schoolName, RoleName = StaticRoleNames.Host.Student, GroupInfo = group }
                    };
                }
            }
            throw new AbpCustomStatusCodeException(204, "用户名或密码错误,请重试");
        }

        /// <summary>
        /// 老师登录接口
        /// </summary>
        /// <param name="account"></param>
        /// <returns></returns>
        public async Task<LoginDto> TeacherLogin(AccountDto account)
        {
            if (account == null) throw new AbpValidationException();
            var loginResult = await _login.LoginAsync(account.UserNameOrEmailAddress, account.Password, GetTenancyNameOrNull());
            if (loginResult.Result == AbpLoginResultType.Success)
            {
                var roleName = await UserManager.GetRolesAsync(loginResult.User);
                if (roleName.Contains(StaticRoleNames.Host.Teacher))
                {
                    var accessToken = GetEncryptedAccessToken(CreateAccessToken(CreateJwtClaims(loginResult.Identity)));
                    var teacher = await _teacherContext.FirstOrDefaultAsync(x => x.UserID == loginResult.User.Id);
                    var schoolName = (await _schoolContext.SingleAsync(x => x.Id == teacher.SchoolID)).Name;
                    return new LoginDto
                    {
                        AccessToken = accessToken,
                        ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds,
                        User = new UserInfoDto { Name = teacher.Name, SchoolName = schoolName, RoleName = StaticRoleNames.Host.Teacher }
                    };
                }
            }
            throw new AbpCustomStatusCodeException(204, "用户名或密码错误,请重试");
        }


        private string GetTenancyNameOrNull()
        {
            if (!AbpSession.TenantId.HasValue)
            {
                return null;
            }
            return _tenantCache.GetOrNull(AbpSession.TenantId.Value)?.TenancyName;
        }

        private string CreateAccessToken(IEnumerable<Claim> claims, TimeSpan? expiration = null)
        {
            var now = DateTime.UtcNow;

            var jwtSecurityToken = new JwtSecurityToken(
                issuer: _configuration.Issuer,
                audience: _configuration.Audience,
                claims: claims,
                notBefore: now,
                expires: now.Add(expiration ?? _configuration.Expiration),
                signingCredentials: _configuration.SigningCredentials
            );

            return new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
        }

        private static List<Claim> CreateJwtClaims(ClaimsIdentity identity)
        {
            var claims = identity.Claims.ToList();
            var nameIdClaim = claims.First(c => c.Type == ClaimTypes.NameIdentifier);

            // Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims.
            claims.AddRange(new[]
            {
                new Claim(JwtRegisteredClaimNames.Sub, nameIdClaim.Value),
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.Now.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
            });

            return claims;
        }

        private string GetEncryptedAccessToken(string accessToken)
        {
            return SimpleStringCipher.Instance.Encrypt(accessToken, AppConsts.DefaultPassPhrase);
        }
    }
}
